﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;


namespace HaSdkWrapper
{
    public class HaCamera
    {
        private IntPtr _cam = IntPtr.Zero;

        #region callbacks
        private HA_DecodeImageCb_t _decodeImageCallback;
        private HA_DataReadCb_t _lprInfoCapturedCallback;
        private HA_ConnectEventCb_t _connectEventCallback;
        private HA_ReadTSerialCb_t _serialReadCallback;
        private HA_IOStateCb_t _ioStateCallback;
        private HA_PlateInfoCb_t _offlineLprInfoCallback;
        #endregion


        private static bool initialized;
        private static readonly object initLock = new object();


        #region event

        //public event EventHandler<ImageDecodedEventArg> ImageDecoded;


        /// <summary>
        /// 车辆抓拍事件，有车辆被抓拍的时候产生该事件
        /// </summary>
        public event EventHandler<VehicleCapturedEventArgs> VehicleCaptured;


        /// <summary>
        /// 连接状态发生变化
        /// </summary>
        public event EventHandler<ConnectEventArgs> ConnectStateChanged;

        /// <summary>
        /// 读取到串口数据
        /// </summary>
        public event EventHandler<SerialDataEventArgs> SerialDataRead;

        /// <summary>
        /// IO口状态发生变化
        /// </summary>
        public event EventHandler<IoStateChangedEventArgs> IoStateChanged;


        /// <summary>
        /// 离线抓拍的车辆信息上传事件
        /// </summary>
        public event EventHandler<VehicleCapturedEventArgs> OfflineVehicleCaptured;

        #endregion


        #region properties

        /// <summary>
        /// 相机ip
        /// </summary>
        public string Ip { get; set; }


        /// <summary>
        /// 相机的端口号，缺省为9527
        /// </summary>
        public int Port { get; set; }


        public object Tag { get; set; }


        /// <summary>
        /// 相机是否已经连接成功
        /// </summary>
        public bool IsConnected => HaSdk.HA_Connected(_cam);


        /// <summary>
        /// 相机的句柄，用来调用底层代码的时候使用
        /// </summary>
        public IntPtr Cam
        {
            get { return _cam; }
        }

        #endregion


        #region methods

        public HaCamera()
        {
            this.Port = 9527;
        }

        /// <summary>
        /// 连接相机
        /// </summary>
        /// <param name="hwnd">窗口的句柄，显示实时视频用，不需要显示视频的时候，传入IntPtr.Empty</param>
        public void Connect(IntPtr hwnd)
        {
            lock (initLock)
            {
                if (!initialized)
                {
                    HaSdk.HA_Init();
                    HaSdk.HA_SetCamType(CAM_TYPE.CAM_TYPE_TI8127);
                    HaSdk.HA_SetCharEncode(CHAR_ENCODE.CHAR_ENCODE_GBK);
                    HaSdk.HA_SetNotifyConnected(true);
                    initialized = true;
                }
            }


            if (HaSdk.HA_Connected(_cam))
            {
                HaSdk.HA_DisConnect(_cam);
            }

            if (string.IsNullOrEmpty(Ip))
            {
                throw new InvalidOperationException("Ip没有设置");
            }

            if (Port == 0)
            {
                throw new InvalidOperationException("端口号没有设置");
            }

            int errorNum = 0;
            _cam = HaSdk.HA_ConnectEx(Ip, (ushort)Port, "", "", ref errorNum, 0, true);
            if (_cam == IntPtr.Zero)
            {
                throw new Exception("无法连接相机, errorNum: " + errorNum);
            }

            HookCallbackEx(_cam);


            //开始显示实时画面
            //如果不需要显示实时画面，将第二个参数传IntPtr.Zero
            HaSdk.HA_StartStreamEx(_cam, hwnd, null, 0);


        }


        /// <summary>
        /// 停止播放实时画面
        /// </summary>
        public void StopLiveVideo()
        {
            HaSdk.HA_StopStream(_cam);
        }


        /// <summary>
        /// 断开和相机的连接
        /// </summary>
        public void DisConnect()
        {
            HaSdk.HA_ClearAllCallbacks(_cam);

            HaSdk.HA_DisConnect(_cam);
        }


        /// <summary>
        /// 手动触发抓拍
        /// </summary>
        /// <returns></returns>
        public int Trigger()
        {
            return HaSdk.HA_Trigger(_cam, 0);
        }

        /// <summary>
        /// 读取相机的当前时间
        /// </summary>
        /// <returns></returns>
        public DateTime GetDateTime()
        {
            int y = 0, m = 0, d = 0, h = 0, min = 0, s = 0;
            var res = HaSdk.HA_GetSysTimeEx(_cam, ref y, ref m, ref d, ref h, ref min, ref s);
            CheckResult(res);
            return new DateTime(y, m, d, h, min, s);
        }


        /// <summary>
        /// 设置相机时间
        /// </summary>
        /// <param name="dateTime">时间</param>
        public int SetDateTime(DateTime dateTime)
        {
            return HaSdk.HA_SetSysTimeEx(_cam, dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second);
        }


        /// <summary>
        /// 写串口数据
        /// </summary>
        /// <param name="index">从1开始，目前只支持一路串口</param>
        /// <param name="data">要写入的数据</param>
        public int WriteSerialData(int index, byte[] data)
        {
            return HaSdk.HA_WriteTSerial(_cam, index, data);
        }


        /// <summary>
        /// 截图
        /// </summary>
        /// <returns></returns>
        public byte[] CaptureImage()
        {
            CheckConnected();

            var buf = new byte[2 * 1024 * 1024];
            var len = 0;
            var res = HaSdk.HA_CapImgToBuffer(_cam, buf, ref len);
            if (res != 0)
            {
                throw new Exception(string.Format("截图错误，error={0}", res));
            }
            var img = new byte[len];
            Buffer.BlockCopy(buf, 0, img, 0, img.Length);
            return img;
        }

        /// <summary>
        /// 获取所有白名单
        /// </summary>
        /// <returns></returns>
        public IList<HA_AllowList> GetAllowLists()
        {
            var list = new List<HA_AllowList>();
            var res = HaSdk.HA_GetWhiteList(_cam, list);
            CheckResult(res);
            return list;
        }

        /// <summary>
        /// 增加白名单
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int AddWhiteList(HA_AllowList item)
        {
            return HaSdk.HA_AddWhiteList(_cam, item);
        }

        /// <summary>
        /// 删除指定的白名单
        /// </summary>
        /// <param name="plate"></param>
        /// <returns></returns>
        public int DeleteWhiteList(string plate)
        {
            return HaSdk.HA_DeletehiteListByPlate(_cam, plate);
        }

        /// <summary>
        /// 清除所有白名单
        /// </summary>
        /// <returns></returns>
        public int ClearWhiteList()
        {
            return HaSdk.HA_ClearWhiteList(_cam);
        }

        /// <summary>
        /// 读取序列号
        /// </summary>
        /// <returns></returns>
        public string GetSerialNumber()
        {
            CheckConnected();
            string sn;
            var res = HaSdk.HA_GetCustomerSN(_cam, out sn);
            CheckResult(res);
            return sn;
        }


        /// <summary>
        /// 写用户数据
        /// </summary>
        /// <param name="userData"></param>
        public int WriteUserData(string userData)
        {
            CheckConnected();
            return HaSdk.HA_WriteCustomerAuthCode(_cam, userData);
        }


        /// <summary>
        /// 读用户数据
        /// </summary>
        /// <returns></returns>
        public string ReadUserData()
        {
            string code;
            var res = HaSdk.HA_ReadCustomerAuthCode(_cam, out code);
            CheckResult(res);
            return code;
        }


        /// <summary>
        /// 设置IO口的高低电平状态
        /// </summary>
        /// <param name="index">IO口编号，从0开始</param>
        /// <param name="state">1: 高电平， 0：低电平</param>
        /// <returns></returns>
        public int SetIOState(int index, int state)
        {
            return HaSdk.HA_SetIOState(_cam, index, state);
        }


        /// <summary>
        /// 打开道闸
        /// </summary>
        /// <param name="index">IO口编号，从0开始</param>
        /// <param name="durationMilliSecond">持续时间（毫秒），取值范围500-5000ms</param>
        /// <returns></returns>
        public int SetIOOutputAuto(int index, int durationMilliSecond)
        {
            return HaSdk.SetIOOutputAuto(_cam, index, durationMilliSecond);
        }


        /// <summary>
        /// 播放音频文件
        /// </summary>
        /// <param name="waveBytes">音频文件的buffer，格式：压缩参数：A-Law, 8000Hz,64kbps,mono</param>
        /// <returns></returns>
        public int PlayAudio(byte[] waveBytes)
        {
            IntPtr mem = Marshal.AllocHGlobal(waveBytes.Length);
            Marshal.Copy(waveBytes, 0, mem, waveBytes.Length);
            var res = HaSdk.PlayAudio(_cam, mem, waveBytes.Length);
            Marshal.FreeHGlobal(mem);
            return res;
        }


        /// <summary>
        /// 获取系统的配置参数
        /// </summary>
        /// <returns></returns>
        public config_gateway GetGatewayConfig()
        {
            var cfg = new config_gateway();
            var res = HaSdk.HA_GetGatewayConfig(_cam, ref cfg);
            CheckResult(res);
            return cfg;
        }



        /// <summary>
        /// 设置系统的配置参数
        /// </summary>
        /// <param name="cfg"></param>
        public int SetGatewayConfig(config_gateway cfg)
        {
            return HaSdk.HA_SetGatewayConfig(_cam, ref cfg);

        }


        /// <summary>
        /// 根据时间段查询离线记录数量
        /// </summary>
        /// <param name="from">起始时间</param>
        /// <param name="to">结束时间</param>
        /// <returns></returns>
        public int QueryOfflineCountByTime(DateTime from, DateTime to)
        {
            if (from >= to)
            {
                throw new ArgumentException("from的时间应该小于to的时间");
            }

            return HaSdk.HA_QueryOfflineCountByTime(_cam, from, to);
        }

        DateTime _lastSaveTime = DateTime.MinValue;
        private void onImageDecoded(IntPtr cam, IntPtr rgb, int width, int height, int userParm)
        {


            //if (ImageDecoded != null && haCam != null)
            //{
            //    var now = DateTime.Now;
            //    if (now - haCam._lastSaveTime >= TimeSpan.FromSeconds(1))
            //    {
            //        var buffer = Marshal.AllocHGlobal(2 * 1024 * 1024);
            //        uint len = 0;
            //        HaSdk.HA_EncodeJpeg(rgb, (uint)width, (uint)height, buffer, ref len);
            //        if (len > 0)
            //        {
            //            var bytes = new byte[len];
            //            Marshal.Copy(buffer, bytes, 0, (int)len);

            //            var img = Image.FromStream(new MemoryStream(bytes));
            //            var arg = new ImageDecodedEventArg { Image = img };
            //            haCam.OnImageDecoded(arg);

            //            haCam._lastSaveTime = now;
            //        }

            //        Marshal.FreeHGlobal(buffer);
            //    }

            //}

        }
        #endregion

        #region callbacks
        private void OnDataReceived(IntPtr cam, string ip, ushort port, int dataType, IntPtr data, uint dataLen, int usrParam)
        {

            switch (dataType)
            {
                case HaSdkConstants.MESSAGE_ID_CAR:
                    if (VehicleCaptured != null)
                    {
                        var pkg = (pkg_vehicle)Marshal.PtrToStructure(data, typeof(pkg_vehicle));
                        FireVehicleCapturedEvent(cam, pkg);
                    }


                    break;
            }

        }


        private void ConnectEventCb(IntPtr cam, string ip, ushort port, int evt, int usrParam)
        {
            var e = new ConnectEventArgs { Connected = evt == 1 };
            OnConnectStateChanged(e);
        }



        private void SerialReadCb(IntPtr cam, int index, IntPtr data, int size, int usrParam)
        {
            var buf = new byte[size];
            Marshal.Copy(data, buf, 0, size);
            var e = new SerialDataEventArgs() { Data = buf, Index = index };
            OnSerialDataRead(e);
        }


        private void IoStateCb(IntPtr cam, int index, int state, IntPtr usrParam)
        {
            var e = new IoStateChangedEventArgs() { Index = index, State = state };
            OnIoStateChanged(e);
        }


        private void OfflineLprInfoCb(IntPtr cam, IntPtr vehicle, int usrParam)
        {
            if (OfflineVehicleCaptured != null)
            {
                var pkg = (pkg_vehicle)Marshal.PtrToStructure(vehicle, typeof(pkg_vehicle));
                var arg = ConvertVehicleToCapturedEventArgs(pkg);
                arg.IsOffline = true;
                OnOfflineVehicleCaptured(arg);
            }
        }


        #endregion

        private void FireVehicleCapturedEvent(IntPtr intPtr, pkg_vehicle plate)
        {
            var e = ConvertVehicleToCapturedEventArgs(plate);

            this.OnLprInfoCaptured(e);
        }

        private VehicleCapturedEventArgs ConvertVehicleToCapturedEventArgs(pkg_vehicle plate)
        {
            VehicleCapturedEventArgs e = new VehicleCapturedEventArgs();
            e.License = plate.plate;
            e.Color = Converter.ConvertColor(plate.plate_color);
            e.LprType = LprType.Unknown;
            e.Confidence = Convert.ToInt16(plate.plate_confidence);
            e.PlateTime = Converter.ConvertToDateTime(plate.img_time_sec[0], plate.img_time_usec[0]);
            if (!string.IsNullOrEmpty(plate.plate))
            {
                e.PlatePosition = new Rectangle(plate.plate_x, plate.plate_y, plate.plate_w, plate.plate_h);
            }
            e.Ip = this.Ip;
            e.ResultType = ResultType.Stable;
            e.LprId = plate.sequence_id;

            if (plate.img_num > 0)
            {
                var ptr = plate.img[0];
                var len = plate.img_len[0];
                var mem = new byte[len];
                Marshal.Copy(ptr, mem, 0, (int)len);
                e.JpegBuffer = mem;
            }

            if (plate.plate_img_num > 0)
            {
                var len = plate.plate_img_len[0];
                var mem = new byte[len];
                Marshal.Copy(plate.plate_img[0], mem, 0, mem.Length);
                e.PlateJpegBuffer = mem;
            }

            return e;
        }


        private void HookCallbackEx(IntPtr cam)
        {
            _lprInfoCapturedCallback = OnDataReceived;
            HaSdk.HA_RegDataReadCbEx(cam, _lprInfoCapturedCallback, 0);

            _connectEventCallback = ConnectEventCb;
            HaSdk.HA_RegConnectEventCbEx(cam, _connectEventCallback, 0);

            _serialReadCallback = SerialReadCb;
            HaSdk.HA_RegReadTSerialCbEx(cam, _serialReadCallback, 0);

            _ioStateCallback = IoStateCb;
            HaSdk.HA_RegIOStateCb(cam, _ioStateCallback, IntPtr.Zero);


            _offlineLprInfoCallback = OfflineLprInfoCb;
            HaSdk.HA_SetOfflinePlateInfoCb(cam, _offlineLprInfoCallback, 0);
        }

        protected virtual void OnLprInfoCaptured(VehicleCapturedEventArgs e)
        {
            VehicleCaptured?.Invoke(this, e);
        }


        private void CheckConnected()
        {
            var connected = HaSdk.HA_Connected(this._cam);
            if (!connected)
            {
                throw new InvalidOperationException("没有连接相机");
            }

        }

        private void CheckResult(int result)
        {
            if (result != HaSdkConstants.ERR_NONE)
            {
                throw new Exception($"调用函数发生错误, 错误代码:{result}");
            }
        }


        protected virtual void OnConnectStateChanged(ConnectEventArgs e)
        {
            ConnectStateChanged?.Invoke(this, e);
        }

        protected virtual void OnSerialDataRead(SerialDataEventArgs e)
        {
            SerialDataRead?.Invoke(this, e);
        }

        protected virtual void OnIoStateChanged(IoStateChangedEventArgs e)
        {
            IoStateChanged?.Invoke(this, e);
        }

        protected virtual void OnOfflineVehicleCaptured(VehicleCapturedEventArgs e)
        {
            OfflineVehicleCaptured?.Invoke(this, e);
        }
    }
}
